import { useMemberStore } from '@/stores'

const baseURL = 'https://pcapi-xiaotuxian-front-devtest.itheima.net'

let loadingCount = 0 // 请求计数器
// 拦截器配置
const httpInterceptor = {
  // 拦截前触发（其实就是请求拦截器）
  // UniApp.RequestOptions 可以在 uni.request 的参数类型中看到，是一个包含了 url，data 等属性的对象接口
  invoke(options: UniApp.RequestOptions) {
    // 显示加载中，此时如果同时发送5个请求，只有第一个请求会打开加载动画，其他请求不会
    if (loadingCount === 0) {
      uni.showLoading()
    }
    loadingCount++ // 计数器自增
    // 1. 非 http 开头需拼接地址
    if (!options.url.startsWith('http')) {
      options.url = baseURL + options.url
    }
    // 2. 请求超时
    // list页面的数据，10秒请求不到
    options.timeout = 60000
    // 3. 添加小程序端请求头标识
    options.header = {
      'source-client': 'miniapp',
      ...options.header, // 先执行 ...options.header, 再执行 options.header = {...}
    }
    // 4. 添加 token 请求头标识
    const memberStore = useMemberStore()
    const token = memberStore.profile?.token
    if (token) {
      options.header.Authorization = token
    }
  },
  complete() {
    // complete 会在请求完成时触发，无论成功或者失败，都会执行，
    // 并且 requestTask.abort 中断的请求也会执行 complete
    loadingCount-- // 计数器自减
    // 此时会在所有请求结束后再关闭加载动画
    if (loadingCount === 0) {
      uni.hideLoading() // 最后一个请求完成时关闭动画
    }
  },
}

// 拦截 request 请求
uni.addInterceptor('request', httpInterceptor)
// 拦截 uploadFile 文件上传
uni.addInterceptor('uploadFile', httpInterceptor)

interface Data<T> {
  code: string
  msg: string
  result: T
}

// 请求配置类型（自动推导泛型）
interface RequestConfig<T> {
  options: UniApp.RequestOptions
  resolve: (value: Data<T>) => void
  reject: (reason?: unknown) => void
}

// 统一请求配置生成器
const createRequestConfig = <T>({
  options,
  resolve,
  reject,
}: RequestConfig<T>): UniApp.RequestOptions => {
  return {
    ...options,
    success(res) {
      if (res.statusCode >= 200 && res.statusCode < 300) {
        resolve(res.data as Data<T>)
      } else if (res.statusCode === 401) {
        const memberStore = useMemberStore()
        memberStore.clearProfile()

        // 获取当前页面路由
        const pages = getCurrentPages()
        const currentPage = pages[pages.length - 1] as any

        // 获取当前页面路径，并且进行编码，因为路径中可能带有 ?id=123 这种参数，所以需要进行编码
        // 这里其实可能有 bug，因为页面请求是在 setup 钩子发起的，如果请求失败的时候还没执行到 onLoad 钩子的情况下，
        // onLoad 钩子之后，页面实例是存在的，fullPath 也是存在的，但是在这之前页面实例就还不存在，
        // fullPath 也不存在，但是一般请求失败没那么快吧，所以这里先不考虑这个 bug
        const currentPath = currentPage.$page?.fullPath
          ? '?toUrl=' + encodeURIComponent(currentPage.$page.fullPath)
          : ''
        uni.redirectTo({
          url: '/pages/login/login' + currentPath,
        })
        reject(res) // 把空闲的 promise 拒绝掉，不要占内存
      } else {
        uni.showToast({
          icon: 'none',
          title: (res.data as Data<T>).msg || '数据获取失败',
        })
        reject(res)
      }
    },
    fail(err) {
      uni.showToast({
        icon: 'none',
        title: '网络错误，换个网络试试',
      })
      reject(err)
    },
  }
}

// 标准请求封装
export const http = <T>(options: UniApp.RequestOptions) => {
  return new Promise<Data<T>>((resolve, reject) => {
    uni.request(createRequestConfig<T>({ options, resolve, reject }))
  })
}

// 支持中断的长请求
export const httpLong = <T>(options: UniApp.RequestOptions) => {
  let requestTask: UniApp.RequestTask

  const promise = new Promise<Data<T>>((resolve, reject) => {
    requestTask = uni.request({
      ...createRequestConfig<T>({ options, resolve, reject }),
      complete() {},
    })
  })

  return {
    promise,
    abort: () => {
      // 这里 requestTask 是一个对象，调用 abort 方法的时候 this 是 requestTask，
      // abort 在运行的时候会访问 this.promise，所以这里要保证 this 指向 requestTask
      requestTask.abort()
    },
  }
}
